package com.kgar.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.huaban.analysis.jieba.JiebaSegmenter;
import com.huaban.analysis.jieba.WordDictionary;
import com.kgar.dao.ResumeDao;
import com.kgar.dto.UserDTO;
import com.kgar.entity.graph.node.*;
import com.kgar.entity.graph.relationship.MatchWithRelation;
import com.kgar.entity.graph.relationship.RelevantToRelation;
import com.kgar.entity.pojo.*;
import com.kgar.repository.*;
import com.kgar.service.AsyncService;
import com.kgar.service.JobInfoService;
import com.kgar.util.LoadUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.util.CastUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.kgar.util.RedisConstants.*;

@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {

    @Resource
    private ResumeDao resumeDao;

    @Resource
    private ResourceLoader resourceLoader;

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisTemplate<String, List<String>> redisTemplate;

    @Resource
    private ResumeRepository resumeRepository;

    @Resource
    private UserRepository userRepository;

    @Resource
    private MatchWithRepository matchWithRepository;

    @Resource
    private RecruiterRepository recruiterRepository;

    @Resource
    private JobPositionRepository jobPositionRepository;

    @Resource
    private JobInfoDocRepository jobInfoDocRepository;

    @Resource
    private CompanyRepository companyRepository;

    @Resource
    private JobInfoService jobInfoService;

    @Resource
    private RelevantToRepository relevantToRepository;

    @Resource
    private EducationRepository educationRepository;

    @Resource
    private ResumeDocRepository resumeDocRepository;


    @Override
    @Async
    public void asyncGetBestJob(UserDTO userDTO) {
        //获取用户信息
        Integer userId = userDTO.getUserId();
        //对简历分词
        List<String> resumeExtract = resumeExtract(userId);

        //获取标签词库
        List<String> words = getWords();

        //计算简历与词库的相似分数并存储到redis
        calcScoreAndStored(userId, resumeExtract, words);
    }

    @Override
    @Async
    public void createOrUpdateRelationWithResume2Job(UserDTO userDTO, Resume resume, List<JobInfo> jobInfos) {

        // 先从知识图谱中查找简历节点
        ResumeNode resumeNode = resumeRepository.findByResumeId(resume.getResumeId());

        // 如果简历节点存在，则执行后续操作
        if (resumeNode != null) {

            // 构建职位ID到JobInfo对象的映射
            Map<Integer, JobInfo> jobIdToJobInfoMap = jobInfos.stream()
                    .collect(Collectors.toMap(JobInfo::getJobId, jobInfo -> jobInfo));

            // 获取所有待处理的职位ID
            Set<Integer> jobIds = jobIdToJobInfoMap.keySet();

            // 查找并处理简历与每个职位的关系
            for (Integer jobId : jobIds) {
                MatchWithRelation matchWithRelation = matchWithRepository.updateOrCreateMatchWithRelation(
                        resume.getResumeId(),
                        jobId.longValue(),
                        jobIdToJobInfoMap.get(jobId).getScore());

                stringRedisTemplate.opsForHash().put(JOB_MATCH_SCORE + userDTO.getUserId(), jobId.toString(), matchWithRelation.getMatchScore().toString());
            }

        }
    }

    @Override
    @Async
    public void createRelationWithUser2Resume(UserDTO userDTO, Resume resume) {

        UserNode userNode = getUserNode(userDTO);

        // 创建新的简历节点并设置属性
        ResumeNode newResumeNode = new ResumeNode();
        newResumeNode.setResumeId(resume.getResumeId());
        newResumeNode.setUserId(userDTO.getUserId());
        newResumeNode.setResumeName(resume.getResumeName());
        List<String> tagNameList = resume.getTags() == null ? new ArrayList<>() :
                Stream.of(
                                resume.getTags().getPosTags(),
                                resume.getTags().getPosTypes(),
                                resume.getTags().getSkillsTags(),
                                resume.getTags().getIndustries()
                        )
                        .filter(Objects::nonNull)
                        .flatMap(list -> list.stream().map(Tags::getTagName))
                        .collect(Collectors.toList());

        newResumeNode.setTags(tagNameList);

        // 建立简历与用户之间的联系
        userNode.setResumeNodes(ListUtil.toList(newResumeNode));

        // 保存到知识图谱中
        userRepository.save(userNode);
    }

    @NotNull
    private UserNode getUserNode(UserDTO userDTO) {
        // 查找或新建用户节点
        UserNode userNode;
        if (userRepository.existsById(userDTO.getUserId())) {
            userNode = userRepository.findById(userDTO.getUserId()).orElseGet(() -> {
                UserNode newUserNode = new UserNode();
                newUserNode.setUserId(userDTO.getUserId());
                newUserNode.setUserName(userDTO.getUserName());
                return newUserNode;
            });
        } else {
            userNode = new UserNode();
            userNode.setUserId(userDTO.getUserId());
            userNode.setUserName(userDTO.getUserName());
            userRepository.save(userNode);
            log.info(userDTO.getUserName() + " 用户节点已保存到知识图谱");
        }
        return userNode;
    }

    @Override
    @Async
    public void createUserNode(UserDTO userDTO) {
        getUserNode(userDTO);
    }


    //
    @Override
    public void createRecruiterNode(Recruiter recruiter) {
        Integer userId = recruiter.getUserId();
        RecruiterNode recruiterNode = new RecruiterNode();
        recruiterNode.setRecruitId(userId);
        Optional<UserNode> userNodeOptional = userRepository.findById(recruiter.getUserId());
        if (userNodeOptional.isPresent()) {
            UserNode userNode = userNodeOptional.get();
            recruiterNode.setUserNode(userNode);
            recruiterRepository.save(recruiterNode);
        }
    }

    @Override
    public void createOrUpdateJobPositionNode(Recruiter one, JobInfo jobInfo) {
        // 查找或新建招聘者节点
        boolean b = recruiterRepository.existsById(one.getUserId());

        if (b) {
            try {
                //给jobInfo添加标签属性
                handlerJobInfo(jobInfo);
                //保存jobInfo到mysql
                saveOrUpdateJobInfo2Mysql(jobInfo);
                //保存jobInfo到es
                saveOrUpdateJobInfoDoc2ES(jobInfo);

                Optional<CompanyNode> companyNodeOptional = companyRepository.findByCompanyNameAndAddress(jobInfo.getCompany(), jobInfo.getAddress());
                CompanyNode companyNode;
                if (companyNodeOptional.isEmpty()) {
                    companyNode = new CompanyNode();
                    companyNode.setCompanyName(jobInfo.getCompany());
                    companyNode.setAddress(jobInfo.getAddress());
                } else
                    companyNode = companyNodeOptional.get();

                Optional<EducationNode> educationNodeOptional = educationRepository.findByEducationName(jobInfo.getEducation());
                EducationNode educationNode;
                if (educationNodeOptional.isEmpty()) {
                    educationNode = new EducationNode();
                    educationNode.setEducationName(jobInfo.getEducation());
                } else
                    educationNode = educationNodeOptional.get();

                JobPositionNode jobPositionNode = new JobPositionNode();
                jobPositionNode.setJobPositionId(jobInfo.getJobId().longValue());
                jobPositionNode.setTitle(jobInfo.getTitle());
                jobPositionNode.setDescription(jobInfo.getDescription());
                jobPositionNode.setHiring_manager(jobInfo.getHiringManager());
                jobPositionNode.setSalaryRange(jobInfo.getSalary());
                jobPositionNode.setCompany(companyNode);
                jobPositionNode.setEducation(educationNode);
                jobPositionNode.setTag(JSONUtil.toList(jobInfo.getTags(), String.class));
                Optional<RecruiterNode> byId = recruiterRepository.findById(one.getUserId());
                if (byId.isPresent()) {
                    RecruiterNode recruiterNode = byId.get();
                    recruiterNode.setJobPositionNodes(List.of(jobPositionNode));
                    //保存知识图谱中的招聘者和职位以及公司信息
                    recruiterRepository.save(recruiterNode);
                }
                System.out.println(jobPositionNode);


            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
    }

    @Override
    public void saveOrUpdateJobInfo2Mysql(JobInfo jobInfo) {
        Object tagList = redisTemplate.opsForHash().get(JOB_TAG_KEY, jobInfo.getJobId().toString());
        jobInfo.setTags(JSONUtil.toJsonStr(tagList));
        jobInfoService.saveOrUpdate(jobInfo);
    }

    @Override
    public void saveOrUpdateJobInfoDoc2ES(JobInfo jobInfo) {
        JobInfoDoc jobInfoDoc = new JobInfoDoc(jobInfo);
        JobInfoDoc save = jobInfoDocRepository.save(jobInfoDoc);
        log.info("es搜索引擎已保存职位数据,jobId: " + save.getJobId());
    }

    @Override
    public void createOrUpdateRelationWithJob2Resume(UserDTO user, JobInfo info, List<Resume> resumeList) {
        // 先从知识图谱中查找职位节点
        JobPositionNode jobPositionNode = jobPositionRepository.findByJobId(info.getJobId().longValue());

        // 如果职位节点存在，则执行后续操作
        if (jobPositionNode != null) {

            // 构建简历id到Resume对象的映射
            Map<Integer, Resume> resumeIdTOResumeMap = resumeList.stream()
                    .collect(Collectors.toMap(Resume::getResumeId, resume -> resume));

            // 获取所有待处理的简历id
            Set<Integer> resumeIds = resumeIdTOResumeMap.keySet();


            // 查找并处理简历与每个职位的关系
            for (Integer resumeId : resumeIds) {
                ResumeNode byResumeId = resumeRepository.findByResumeId(resumeId);
                if(byResumeId!=null){

                RelevantToRelation relevantToRelation = relevantToRepository.updateOrCreateRelevantToRelation(
                        info.getJobId().longValue(),
                        resumeId,
                        resumeIdTOResumeMap.get(resumeId).getScore());

                stringRedisTemplate.opsForHash().put(RESUME_RELEVANT_SCORE + user.getUserId(), resumeId.toString(), relevantToRelation.getRelevanceScore().toString());
                }
            }

        }
    }

    @Override
    public void deleteJobFromNeo4j(UserDTO user, Integer jobId) {
        recruiterRepository.deleteRelationWithRecruiter2Job(user.getUserId(), jobId.longValue());
    }

     private void updateJobFromNeo4j(JobInfo jobInfo) {
        Optional<JobPositionNode> jobPositionNodeOptional = jobPositionRepository.findById(jobInfo.getJobId().longValue());
        if (jobPositionNodeOptional.isPresent()) {
            JobPositionNode jobPositionNode = jobPositionNodeOptional.get();
            jobPositionNode.setLink(jobInfo.getLink());
            jobPositionNode.setTag(JSONUtil.toList(jobInfo.getTags(), String.class));
            jobPositionNode.setSalaryRange(jobInfo.getSalary());
            jobPositionNode.setLast_active(jobInfo.getLastActive());
            jobPositionNode.setDescription(jobInfo.getDescription());
            jobPositionNode.setTitle(jobInfo.getTitle());
            jobPositionNode.setHiring_manager(jobInfo.getHiringManager());
            CompanyNode company = jobPositionNode.getCompany();
            company.setCompanyName(jobInfo.getCompany());
            company.setAddress(jobInfo.getAddress());
            jobPositionNode.setCompany(company);
            EducationNode education = jobPositionNode.getEducation();
            education.setEducationName(jobInfo.getEducation());
            jobPositionRepository.save(jobPositionNode);
        }
    }

    private void updateJobFromES(JobInfo jobInfo) {
        jobInfoDocRepository.save(new JobInfoDoc(jobInfo));
    }

    @Override
    public void saveResume2ES(Resume resume) {
        resumeDocRepository.save(new ResumeDoc(resume));
    }

    @Override
    public void deleteResumeFromNeo4j(Integer resumeId) {
        resumeRepository.deleteById(resumeId);
    }

    @Override
    public void deleteResumeFromES(Integer resumeId) {
        resumeDocRepository.deleteById(resumeId.toString());
    }

    @Override
    public void addTags2Mysql(UserDTO user, Resume resume) {
        try {
            handlerResume(resume);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateJobInfo2AllDataBase(JobInfo jobInfo) {
        try {
            //处理标签
            handlerJobInfo(jobInfo);
            //更新到neo4j
            updateJobFromNeo4j(jobInfo);
            //更新到es
            updateJobFromES(jobInfo);
            jobInfoService.saveOrUpdate(jobInfo);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    private void calcScoreAndStored(Integer userId, List<String> resumeExtract, List<String> words) {
        Set<String> set = new HashSet<>();
        // 将用户信息中存在的关键词存入set集合中
        for (String resumeKeyword : resumeExtract) {
            for (String term : words) {
                if (resumeKeyword.equalsIgnoreCase(term)) {
                    set.add(resumeKeyword);
                }

            }
        }
        List<String> list = new ArrayList<>(set);
        //去缓存中根据职位的tag查到对应得职位id
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(JOB_TAG_KEY);

        //计算相似的分数并存入redis中
        Map<String, Double> idScore = new HashMap<>();
        if (!entries.isEmpty()) {
            //遍历每个 优先级和其标签列表 的集合
            entries.forEach((key, value) -> {
                //获取优先级与其职位标签列表 eg: (["java","web前端"]  ["成都"]）
                List<String> list1 = CastUtils.cast(value);
                //将标签列表与用户信息列表求交集
                Collection<String> intersection = CollectionUtil.intersection(list, list1);
                //记录交集个数与其优先级的成绩 作为相似度分数 加点随机值
                double i = intersection.size() * 1.0 + RandomUtil.randomDouble(0.10);
                //将分数存起来
                //职位id 与其分数存入map中
                idScore.put(key.toString(), i);
            });
            // 将Map转换为List<Map.Entry<K,V>>
            List<Map.Entry<String, Double>> list3 = new ArrayList<>(idScore.entrySet());


            //计算相似分数并存入分数前二十的
            // 将前二十个存入redis中
            for (int i = 0; i < Math.min(20, list3.size()); i++) {
                Map.Entry<String, Double> entry = list3.get(i);
                stringRedisTemplate.opsForZSet().add(USER_BEST_JOB + userId, entry.getKey(), entry.getValue() + RandomUtil.randomDouble(0.01));
            }
        }
    }

    @NotNull
    private List<String> getWords() {
        // 加载领域词汇表
        String computerTerms;
        try {
            computerTerms = loadTerms(System.getProperty("user.dir") + "/src/main/resources/static/computer_terms.txt").replaceAll("\\s*", "");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        List<String> terms = List.of(computerTerms.split("、"));

        //↓↓↓↓自定义分词器↓↓↓↓↓
        Path path = Paths.get(System.getProperty("user.dir") + "/src/main/resources/static/自定义分词.txt");
        WordDictionary.getInstance().loadUserDict(path);

        List<String> list1 = new ArrayList<>();
        //去标点
        terms.forEach(term ->
                list1.add(term.replaceAll("\\p{Punct}", ""))
        );
        return list1;
    }

    @NotNull
    private List<String> resumeExtract(Integer userId) {
        //获取用户的简历
        List<Resume> resumes = resumeDao.selectList(Wrappers.lambdaQuery(Resume.class).eq(Resume::getUserId, userId));
        StringBuilder sb = new StringBuilder();
        resumes.forEach(resume -> {

            if (resume.getRawText() != null)
                sb.append(resume.getRawText());

        });
        String s = sb.toString();

        // 使用Jieba进行分词
        JiebaSegmenter segmenter = new JiebaSegmenter();
        List<String> words = segmenter.sentenceProcess(s);

        List<String> list2 = new ArrayList<>();
        words.forEach(word ->
                list2.add(word.replaceAll("\\p{Punct}", ""))
        );
        return list2;
    }

    // 从文件中加载领域词汇表
    private String loadTerms(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = new BufferedReader(new FileReader(filePath));
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line.trim());
        }
        reader.close();
        return sb.toString();
    }


    private void handlerJobInfo(JobInfo jobInfo) throws IOException {
        File file1 = resourceLoader.getResource("classpath:static/computer_terms.txt").getFile();
        // 从文件中加载领域词汇表
        String computerTerms = LoadUtil.loadTerms(file1).replaceAll("\\s*", "");
        List<String> terms = List.of(computerTerms.split("、"));

        // 使用Jieba进行分词
        JiebaSegmenter segment = new JiebaSegmenter();

        List<String> list1 = new ArrayList<>();
        //去标点
        terms.forEach(term ->
                list1.add(term.replaceAll("\\p{Punct}", ""))
        );
        Set<String> set = new HashSet<>(list1);
        // 过滤出计算机领域的词汇


        List<String> keywords = segment.sentenceProcess((jobInfo.getTitle() + jobInfo.getDescription()).replaceAll("\\s*", ""));
        //忽略大小写将应有的标签添加到strings中
        List<String> strings = new ArrayList<>();
        for (String keyword : keywords) {
            for (String s : set) {
                if (s.equalsIgnoreCase(keyword)) {
                    strings.add(s);
                }
            }
        }
        List<String> list = new ArrayList<>(set);
        list.retainAll(strings);
        redisTemplate.opsForHash().put(JOB_TAG_KEY, jobInfo.getJobId().toString(), list);

        jobInfo.setTags(JSONUtil.toJsonStr(list));

    }
    private void handlerResume(Resume resume) throws IOException {
        File file1 = resourceLoader.getResource("classpath:static/computer_terms.txt").getFile();
        // 从文件中加载领域词汇表
        String computerTerms = LoadUtil.loadTerms(file1).replaceAll("\\s*", "");
        List<String> terms = List.of(computerTerms.split("、"));

        // 使用Jieba进行分词
        JiebaSegmenter segment = new JiebaSegmenter();

        List<String> list1 = new ArrayList<>();
        //去标点
        terms.forEach(term ->
                list1.add(term.replaceAll("\\p{Punct}", ""))
        );
        Set<String> set = new HashSet<>(list1);
        // 过滤出计算机领域的词汇


        List<String> keywords = segment.sentenceProcess((resume.getRawText()).replaceAll("\\s*", ""));
        //忽略大小写将应有的标签添加到strings中
        List<String> strings = new ArrayList<>();
        for (String keyword : keywords) {
            for (String s : set) {
                if (s.equalsIgnoreCase(keyword)) {
                    strings.add(s);
                }
            }
        }
        List<String> list = new ArrayList<>(set);
        list.retainAll(strings);

        ResumeNode resumeNode = resumeRepository.findByResumeId(resume.getResumeId());
        resumeNode.setTags(list);
        resumeRepository.save(new ResumeNode());

    }
}
